package net.cyndeline.entitySystem.defaultImpl

import net.cyndeline.entitySystem.EventSystem
import net.cyndeline.entitySystem.EventHandlerSystem
import net.cyndeline.entitySystem.Event
import net.cyndeline.entitySystem.EventReceiver
import net.cyndeline.entitySystem.Component
import net.cyndeline.entitySystem.EntityRef
import net.cyndeline.entitySystem.EventPriority
import collection.mutable.{ HashMap, MultiMap, Set, SynchronizedMap, SynchronizedQueue }
import language.postfixOps
import net.cyndeline.events.gameplay.TakeActionEvent
import net.cyndeline.entitySystem.EventSystemSetup

/**
 * Stores a set of event messages (as well as the entity they were
 * sent to) and processes them in the priority order specified by
 * the handlers.
 */
class DefaultEventSystem extends EventSystem {
  
  /* Mutable synchronized multimap. Use addBinding when adding. */
  private val eventHandlers = 
    new HashMap[Class[_ <: Event], Set[EventHandlerInfo]]() 
    with MultiMap[Class[_ <: Event], EventHandlerInfo]
  
  /* Primary event queue, all events in it will be processed as long
   * as it is non-empty.
   */
  private val events = new SynchronizedQueue[(EntityRef, Event)]()
  
  /* Separate queue for the events that causes a unit to take action. */
  private val takeAction = new SynchronizedQueue[(EntityRef, TakeActionEvent)]()
  
  // TODO Map event-classes to child-events?
  
  /**
   * Keep pushing events to their handlers until the queue run out
   * of events. Event system processing may include additional events being
   * sent to the back of the queue.
   */
  override def process {
    while (events.nonEmpty || takeAction.nonEmpty) {
      
      /* Sending the event to other systems might cause them to send additional
       * events in response. The event queue will only be empty once all 
       * responses have been dequeued and acted on. Then the next action
       * can be made.
       */
      if (events.isEmpty && takeAction.nonEmpty) {
        events enqueue (takeAction dequeue)
      }
      
      val delivery: (EntityRef, Event) = events.dequeue
      send(delivery._1, delivery._2)
    }
  }
  
  override def registerEventHandler[T <: Event](handler: EventHandlerSystem) {
    registerEventHandler(handler, EventPriority.PRIORITY_NORMAL)
  }
  
  override def registerEventHandler[T <: Event](handler: EventHandlerSystem,
		  							   priority: Int) {
    for (eventClass <- handler.events) {
      val currentHandlerSet = (eventHandlers get eventClass) getOrElse Map()
      val nrOfHandlers = currentHandlerSet size
      val handlerInfo = new EventHandlerInfo(handler, priority, nrOfHandlers + 1)
      eventHandlers.addBinding(eventClass, handlerInfo)
    }
    
  }
  
  override def deliverEvent(entity: EntityRef, event: Event) {
    event match {
      case action: TakeActionEvent => takeAction enqueue ((entity, action))
      case other: Event => events enqueue ((entity, other))
    }
  }
  
  override  def clear = {
    eventHandlers.clear
  }
  
  override def setup(setting: EventSystemSetup) {
    setting.setup(this)
  }
  
  private def send(entity: EntityRef, event: Event) {
    val interestedHandlers = eventHandlers.getOrElse(event.getClass, Set())
    val sortedHandlers = interestedHandlers.toList.sortWith(this.handlerSort)
    
    for (handlerInfo <- sortedHandlers) {
      val handler = handlerInfo.handler
      if (handler.enabled && handler.isValidFor(entity)) {
        handler.onEvent(event, entity)
      }
      
      if (event.isCancelled) {
        return
      }
    }
  }
  
  private def handlerSort(a: EventHandlerInfo, b: EventHandlerInfo): Boolean = {
    val ap = a.priority
    val bp = b.priority
    if (ap != bp) {
      return (ap > bp) 
    }
    
    a.insertionOrder < b.insertionOrder
  }
  
  /**
   * Local storage for handlers.
   * @param handler The event handler system.
   * @param priority Event handling priority, handlers handle events in 
   * 				 priority order.
   * @param insertionOrder The number of previously inserted handlers + 1. If
   * 					   two or more handlers with the same priority exists,
   *         			   they will be sorted based on this to ensure that
   *               		   effects in the game always happens in the intended
   *                       (a.k.a. inserted) order.
   */
  private class EventHandlerInfo(val handler: EventHandlerSystem, 
		  							val priority: Int,
		  							val insertionOrder: Int) {
  }
}